/*********************************************************************************
 *      Copyright:  (C) 2022 Zhang Changxing<ZhangChangxingVIP@163.com>
 *                  All rights reserved.
 *
 *       Filename:  serial_init.c
 *    Description:  This file function mainly implements the serial port initialization.
 *                 
 *        Version:  1.0.0(07/28/2022)
 *         Author:  Zhang Changxing <ZhangChangxingVIP@163.com>
 *      ChangeLog:  1, Release initial version on "07/28/2022  01:33:25 AM"
 *                 
 ********************************************************************************/



#include "serial_init.h"


/*--------------------------------------------------------------
 *   description:   Open the serial port File
 *          args:   $ttyusb_ctx:    Serial port property context
 *  return value:   <0: failure,    =0:ok 
 *--------------------------------------------------------------*/
int tty_open(ttyusb_ctx_t *ttyusb_ctx)
{
    if (!ttyusb_ctx)
    {
        log_error("[%s]The argument invalid!\n", __func__);
        return -1;
    }

    ttyusb_ctx->fd = open(ttyusb_ctx->serial_name, O_RDWR|O_NOCTTY|O_NONBLOCK);
    if (ttyusb_ctx->fd < 0)
    {
        log_error("[%s]Open serial file failure:%s\n", __func__, strerror(errno));
        return -2;
    }

    if (!isatty(ttyusb_ctx->fd))
    {
        log_error("[%s]%s is not a terminal equipment!\n", __func__, ttyusb_ctx->serial_name);
        return -3;
    }

    log_info("[%s]Open %s successfully!\n", __func__, ttyusb_ctx->serial_name);
    return 0;
}


/*--------------------------------------------------------------
 *   description:   Closing the serial port File
 *          args:   $ttyusb_ctx:    Serial port property context
 *  return value:   <0: failure,    =0:ok 
 *--------------------------------------------------------------*/
int tty_close(ttyusb_ctx_t *ttyusb_ctx)
{
    int retval = -1;

    if (!ttyusb_ctx)
    {
        log_error("[%s]The argument invalid!\n", __func__);
        return -1;
    }

    retval = tcflush(ttyusb_ctx->fd, TCIOFLUSH);
    if (retval < 0)
    {
        log_error("[%s]Failed to clear the input/output buffer:%s\n", __func__, strerror(errno));
        return -2;
    }
    
    retval = tcsetattr(ttyusb_ctx->fd, TCSANOW, &(ttyusb_ctx->old_termios));
    if (retval < 0)
    {
        log_error("[%s]Set old termios failure:%s\n", __func__, strerror(errno));
        return -3;
    }

    close(ttyusb_ctx->fd);
    
    printf("Excute tty_close() successfully!\n");

    return 0;
}


/*--------------------------------------------------------------
 *   description:   Example Initialize the serial port properties
 *          args:   $ttyusb_ctx:    Serial port property context
 *  return value:   <0: failure,    =0:ok 
 *--------------------------------------------------------------*/
int tty_init(ttyusb_ctx_t *ttyusb_ctx)
{
    int     retval = -1;
    char    baudrate_buf[32] = {0};
    struct  termios new_termios;

    if (!ttyusb_ctx)
    {
        log_error("The argument invalid!\n");
        return -1;
    }

    memset(&new_termios, 0, sizeof(new_termios));
    memset(&(ttyusb_ctx->old_termios), 0, sizeof(ttyusb_ctx->old_termios));

    retval = tcgetattr(ttyusb_ctx->fd, &(ttyusb_ctx->old_termios));
    if (retval < 0)
    {
        log_error("Failed to obtain the current serial port properties!\n");
        return -2;
    }

    retval = tcgetattr(ttyusb_ctx->fd, &new_termios);
    if (retval < 0)
    {
        log_error("Failed to obtain the current serial port properties!\n");
        return -3;
    }

    new_termios.c_cflag |= CLOCAL;//忽略解调器线路状态

    new_termios.c_cflag |= CREAD;

    new_termios.c_cflag &= ~CSIZE;//启动接收器，能够从串口中读取输入数据

    new_termios.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
    /* 
     * ICANON: 标准模式
     * ECHO: 回显所输入的字符
     * ECHOE: 如果同时设置了ICANON标志，ERASE字符删除前一个所输入的字符，WERASE删除前一个输入的单词
     * ISIG: 当接收到INTR/QUIT/SUSP/DSUSP字符，生成一个相应的信号
     *
     * */

    new_termios.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
    /* 
     * BRKINT: BREAK将会丢弃输入和输出队列中的数据(flush)，并且如果终端为前台进程组的控制终端，则BREAK将会产生一个SIGINT信号发送到这个前台进程组
     * ICRNL: 将输入中的CR转换为NL
     * INPCK: 允许奇偶校验
     * ISTRIP: 剥离第8个bits
     * IXON: 允许输出端的XON/XOF流控
     *
     * */

     /* OPOST: 表示处理后输出，按照原始数据输出 */ 
    new_termios.c_oflag &= ~(OPOST);

    if (ttyusb_ctx->baudrate)
    {
        snprintf(baudrate_buf, sizeof(baudrate_buf), "B%d", ttyusb_ctx->baudrate);
        cfsetispeed(&new_termios, (int)baudrate_buf);
        cfsetospeed(&new_termios, (int)baudrate_buf);
    }
    else
    {
        cfsetispeed(&new_termios, B115200);
        cfsetospeed(&new_termios, B115200);
    }

    switch (ttyusb_ctx->databits)
    {
        case 5:
        {
            new_termios.c_cflag |= CS5;
            break;
        }

        case 6:
        {
            new_termios.c_cflag |= CS6;
            break;
        }

        case 7:
        {
            new_termios.c_cflag |= CS7;
            break;
        }

        case 8:
        {
            new_termios.c_cflag |= CS8;
            break;
        }

        default:
        {
            new_termios.c_cflag |= CS8;
            break; 
        }
    }

#if 1
    switch(ttyusb_ctx->parity)
    {
        case 'n':
        case 'N':
        {
            new_termios.c_cflag &= ~PARENB;
            break;
        }

        case 'o':
        case 'O':
        {
            new_termios.c_cflag |= (PARODD | PARENB);
            break;
        }

        case 'e':
        case 'E':
        {
            new_termios.c_cflag &= ~PARENB;
            new_termios.c_cflag &= ~PARODD;
        }

        case 'b':
        case 'B':
        {
            new_termios.c_cflag &= ~PARENB;
            new_termios.c_cflag &= ~CSTOP;
        }

        default:
        {
            new_termios.c_cflag &= ~PARENB;
            break;
        }
    }

    switch(ttyusb_ctx->stopbits)
    {
        case 1:
        {
            new_termios.c_cflag &= ~CSTOPB;
            break;
        }

        case 2:
        {
            new_termios.c_cflag |= CSTOPB;
            break;
        }

        default:
        {
            new_termios.c_cflag &= ~CSTOPB;
            break;
        }
    }
#endif


    //MIN =0 TIME =0 时，如果有数据可读，则read最多返回所要求的字节数，如果无数据可用，则read立即返回0；
    new_termios.c_cc[VTIME] = 0;
    new_termios.c_cc[VMIN] = 0;

    ttyusb_ctx->msend_len = 128;

    retval = tcflush(ttyusb_ctx->fd, TCIOFLUSH);
    if (retval < 0)
    {
        log_error("Failed to clear the input/output buffer:%s\n", strerror(errno));
        return -3;
    }

    retval = tcsetattr(ttyusb_ctx->fd, TCSANOW, &new_termios);
    if(retval < 0)
    {
        log_error("Failed to set new properties of the serial port:%s\n", strerror(errno));
        return -4;
    }

    printf("[%s]Successfully set new properties of the serial port!\n", __func__);
    return 0;
}


/*--------------------------------------------------------------
 *   description:   Sending data to the serial port
 *          args:   $ttyusb_ctx:    Serial port property context
 *                  $send_buf       Buffer address for data to be sent
 *                  $sbuf_len       The length of the send_buf
 *  return value:   <0: failure,    =0:ok 
 *--------------------------------------------------------------*/
int tty_send(ttyusb_ctx_t *ttyusb_ctx, char *send_buf, int sbuf_len)
{
    int     retval = -1;
    int     write_rv = 0;
    char    *ptr = NULL, *end = NULL;


    if (!ttyusb_ctx || !send_buf || (sbuf_len < 0))
    {
        log_error("[%s]The argument invalid!\n", __func__);
        return -1;
    }

    if (sbuf_len > ttyusb_ctx->msend_len)
    {
        ptr = send_buf;
        end = send_buf + sbuf_len;
        do
        {   
            if(ttyusb_ctx->msend_len <(end-ptr))
            {
                retval = write(ttyusb_ctx->fd, ptr, ttyusb_ctx->msend_len);

                if ((retval <= 0) || (retval != ttyusb_ctx->msend_len))
                {
                    log_error("[%s]Write data to fd[%d] failure:%s\n", __func__, ttyusb_ctx->fd, strerror(errno));
                    return -2;
                }

                write_rv += retval;
                ptr += ttyusb_ctx->msend_len;
            }
            else
            {
                retval = write(ttyusb_ctx->fd, ptr, (end - ptr));

                if ((retval <= 0) || (retval != (end - ptr)))
                {
                    log_error("[%s]Write data to fd[%d] failure:%s\n",__func__, ttyusb_ctx->fd, strerror(errno));
                    return -3;
                }

                write_rv += retval;
                ptr += (end - ptr);
            }
            
        }while(ptr < end);
        
    }
    else
    {
        retval = write(ttyusb_ctx->fd, send_buf, sbuf_len);
        if((retval <= 0) || (retval != sbuf_len))
        {
            log_error("[%s]Write data to fd[%d] failure:%s\n", __func__, ttyusb_ctx->fd, strerror(errno));
            return -4;
        }
        write_rv += retval;
    }

    log_info("[%s]send_buf:%s\n", __func__ , send_buf);
    log_info("[%s]write_rv: %d\n", __func__ , write_rv);

    return write_rv;
}



/*--------------------------------------------------------------
 *   description:   Receive data from the serial port
 *          args:   $ttyusb_ctx:    Serial port property context
 *                  $recv_buf       Buffer address for data to be receive
 *                  $rbuf_len       The length of the recv_buf
 *  return value:   <0: failure,    =0:ok 
 *--------------------------------------------------------------*/
int tty_recv(ttyusb_ctx_t *ttyusb_ctx, char *recv_buf, int rbuf_len)
{
    int     read_rv = -1;
    int     rv_fd = -1;
    fd_set  rdset;
    struct  timeval time_out;

    if (!ttyusb_ctx || (rbuf_len < 0))
    {
        log_error("[%s]The argument invalid!\n", __func__);
        return -1;
    }
    
    if (ttyusb_ctx->timeout)
    {
        time_out.tv_sec = (time_t)ttyusb_ctx->timeout;
        time_out.tv_usec = 0;
        FD_ZERO(&rdset);
        FD_SET(ttyusb_ctx->fd, &rdset);

        rv_fd = select(ttyusb_ctx->fd + 1, &rdset, NULL, NULL, &time_out);
        if(rv_fd < 0)
        {
            log_error("[%s]Select() listening for file descriptor error!\n", __func__);
            return -2;
        }
        else if(rv_fd == 0)
        {
            log_error("[%s]Select() listening for file descriptor timeout!\n", __func__);
            return -3;
        }
    }
    
    usleep(1000);
    read_rv = read(ttyusb_ctx->fd, recv_buf, rbuf_len);
    if (read_rv <= 0)
    {
        log_error("[%s]Read data from fd[%d] failure:%s\n", __func__, ttyusb_ctx->fd, strerror(errno));
        return -4;
    }
    
    log_debug("[%s]recv_buf:%s\n", __func__, recv_buf);
    
    return read_rv;
}


